using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Dynamic;
using System.Linq;

namespace OpenCube.Nbt
{
	/// <summary>
	/// Represents a single object with key/value pairs of objects. 
	/// Acts just like normal Dictionary&lt;string, object&gt;, with dynamic support.
	/// </summary>
	[DebuggerDisplay("Count = {Count}")]
	[DebuggerTypeProxy(typeof(DbtDictionaryDebuggerDisplay))]
	public sealed class NbtDictionary
		: DynamicObject, INbtDictionary
	{
		private readonly IDictionary<string, object> _dictionary;

		public NbtDictionary()
		{
			Contract.Ensures(_dictionary != null);
			Contract.Ensures(_dictionary.Count == 0);
			Contract.Ensures(Count == 0);

			_dictionary = new Dictionary<string, object>();
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant(_dictionary != null);
		}

		/// <summary>
		/// Gets the number of key-value pairs in the dictionary.
		/// </summary>
		/// <value>
		/// The number of key-value pairs in the dictionary.
		/// </value>
		public int Count
		{
			get
			{
				Contract.Ensures(Contract.Result<int>() == _dictionary.Count);
				return _dictionary.Count;
			}
		}

		public override bool TryGetMember(GetMemberBinder binder, out object result)
		{
			// Contract.Requires<ArgumentNullException>( binder != null );
			return _dictionary.TryGetValue(binder.Name, out result);
		}

		public override bool TrySetMember(SetMemberBinder binder, object value)
		{
			// Contract.Requires<ArgumentNullException>( binder != null );
			Contract.Ensures(Contract.ValueAtReturn(out value) == _dictionary[binder.Name]);
			Contract.Ensures(Contract.Result<bool>() == true, "Settings should always succeed");
			Contract.Ensures(Count > 0, "After setting at least 1 item should be in the dictionary");

			_dictionary[binder.Name] = value;
			return true;
		}





		IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
		{
			Contract.Ensures(Contract.Result<IEnumerator<KeyValuePair<string, object>>>() != null, "GetEnumerator should always return a value");
			return _dictionary.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			Contract.Ensures(Contract.Result<IEnumerator>() != null, "GetEnumerator should always return a value");
			return _dictionary.GetEnumerator();
		}

		void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
		{
			Contract.Ensures(Count == Contract.OldValue(Count) + 1, "When no exception, the count should be increased by one");
			((ICollection<KeyValuePair<string, object>>)_dictionary).Add(item);
		}

		void ICollection<KeyValuePair<string, object>>.Clear()
		{
			Contract.Ensures(_dictionary.Count == 0, "After clear no items should remain in dictionary");
			Contract.Ensures(Count == 0, "After clear no items should remain in dictionary");

			((ICollection<KeyValuePair<string, object>>)_dictionary).Clear();
		}

		bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
		{
			return ((ICollection<KeyValuePair<string, object>>)_dictionary).Contains(item);
		}

		void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
		{
			((ICollection<KeyValuePair<string, object>>)_dictionary).CopyTo(array, arrayIndex);
		}

		bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
		{
			return ((ICollection<KeyValuePair<string, object>>)_dictionary).Remove(item);
		}

		int ICollection<KeyValuePair<string, object>>.Count
		{
			get
			{
				Contract.Ensures(Contract.Result<int>() == _dictionary.Count);
				return ((ICollection<KeyValuePair<string, object>>)_dictionary).Count;
			}
		}

		bool ICollection<KeyValuePair<string, object>>.IsReadOnly
		{
			get
			{
				Contract.Ensures(Contract.Result<bool>() == false);
				return false;
			}
		}

		/// <summary>
		/// Determines whether the specified key contains key.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>
		///   <c>true</c> if the specified key contains key; otherwise, <c>false</c>.
		/// </returns>
		public bool ContainsKey(string key)
		{
			return _dictionary.ContainsKey(key);
		}

		/// <summary>
		/// Adds the specified key.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <param name="value">The value.</param>
		public void Add(string key, object value)
		{
			Contract.Ensures(Count == Contract.OldValue(Count) + 1, "When no exception, the count should be increased by one");
			_dictionary.Add(key, value);
		}

		/// <summary>
		/// Removes the specified key.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns></returns>
		public bool Remove(string key)
		{
			Contract.Ensures(!Contract.Result<bool>() || Count == Contract.OldValue(Count) - 1, "When return value is true (success) the count should be decreased by one");
			return _dictionary.Remove(key);
		}

		/// <summary>
		/// Tries the get value.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public bool TryGetValue(string key, out object value)
		{
			Contract.Ensures(Contract.Result<bool>() || Contract.ValueAtReturn(out value) == null);
			return _dictionary.TryGetValue(key, out value);
		}

		/// <summary>
		/// Gets or sets the <see cref="System.Object" /> with the specified key.
		/// </summary>
		/// <value>
		/// The <see cref="System.Object" />.
		/// </value>
		/// <param name="key">The key.</param>
		/// <returns></returns>
		public object this[string key]
		{
			get
			{
				Contract.Ensures(Contract.Result<object>() == _dictionary[key]);
				return _dictionary[key];
			}
			set
			{
				Contract.Ensures(_dictionary[key] == value);
				_dictionary[key] = value;
			}
		}

		/// <summary>
		/// Gets the keys.
		/// </summary>
		/// <value>
		/// The keys.
		/// </value>
		public ICollection<string> Keys
		{
			get
			{
				Contract.Ensures(Contract.Result<ICollection<string>>() == _dictionary.Keys);
				return _dictionary.Keys;
			}
		}

		/// <summary>
		/// Gets the values.
		/// </summary>
		/// <value>
		/// The values.
		/// </value>
		public ICollection<object> Values
		{
			get
			{
				Contract.Ensures(Contract.Result<ICollection<object>>() == _dictionary.Values);
				return _dictionary.Values;
			}
		}

		[DebuggerDisplay("{value}", Name = "{key}")]
		private struct KeyValueDebuggerDisplay
		{
			private string key;
			private object value;

			public KeyValueDebuggerDisplay(string key, object value)
			{
				this.value = value;
				this.key = key;
			}
		}

		private sealed class DbtDictionaryDebuggerDisplay
		{
			private readonly NbtDictionary _dictionary;

			public DbtDictionaryDebuggerDisplay(NbtDictionary dictionary)
			{
				Contract.Requires<ArgumentNullException>(dictionary != null);
				_dictionary = dictionary;
			}

			[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
			public KeyValueDebuggerDisplay[] Keys
			{
				get
				{
					var keys = new KeyValueDebuggerDisplay[_dictionary.Count];

					int i = 0;
					foreach (string key in _dictionary.Keys.OrderBy(k => k))
					{
						keys[i] = new KeyValueDebuggerDisplay(key, _dictionary[key]);
						i++;
					}
					return keys;
				}
			}

		}
	}
}